#ifndef ROCKET_COMMEN_LOG_H
#define ROCKET_COMMEN_LOG_H

#include <memory>
#include <queue>
#include <string>
#include <semaphore.h>

#include "config.h"
#include "mutex.h"
#include "../net/timer_event.h"

namespace rocket {

template <typename... Args>
std::string formatString(const char *str, Args &&...args) {
    int size = snprintf(nullptr, 0, str, args...);

    std::string result;
    if (size > 0) {
        result.resize(size);
        snprintf(&result[0], size + 1, str, args...);
    }

    return result;
}

#define DEBUGLOG(str, ...)                                                     \
    if (rocket::Logger::GetGlobalLogger()->getLogLevel() <= rocket::Debug) {   \
        rocket::Logger::GetGlobalLogger()->pushLog(                            \
            (rocket::LogEvent(rocket::LogLevel::Debug)).toString() + "[" +     \
            std::string(__FILE__) + ":" + std::to_string(__LINE__) + "]\t" +   \
            rocket::formatString(str, ##__VA_ARGS__));                         \
        rocket::Logger::GetGlobalLogger()->log();                              \
    }

#define INFOLOG(str, ...)                                                      \
    if (rocket::Logger::GetGlobalLogger()->getLogLevel() <= rocket::Info) {    \
        rocket::Logger::GetGlobalLogger()->pushLog(                            \
            (rocket::LogEvent(rocket::LogLevel::Info)).toString() + "[" +      \
            std::string(__FILE__) + ":" + std::to_string(__LINE__) + "]\t" +   \
            rocket::formatString(str, ##__VA_ARGS__));                         \
        rocket::Logger::GetGlobalLogger()->log();                              \
    }

#define ERRORLOG(str, ...)                                                     \
    if (rocket::Logger::GetGlobalLogger()->getLogLevel() <= rocket::Error) {   \
        rocket::Logger::GetGlobalLogger()->pushLog(                            \
            (rocket::LogEvent(rocket::LogLevel::Error)).toString() + "[" +     \
            std::string(__FILE__) + ":" + std::to_string(__LINE__) + "]\t" +   \
            rocket::formatString(str, ##__VA_ARGS__));                         \
        rocket::Logger::GetGlobalLogger()->log();                              \
    }

enum LogLevel { Unknown = 0, Debug = 1, Info = 2, Error = 3 };

std::string LogLevelToString(LogLevel level);

LogLevel StringToLogLevel(const std::string &log_level);

class LogEvent {
  public:
    LogEvent(LogLevel level) : m_level(level) {}

    std::string getFileName() const { return m_file_name; }

    LogLevel getLogLevel() const { return m_level; }

    std::string toString();

  private:
    std::string m_file_name; // file name
    int32_t m_file_line;     // line number
    int32_t m_pid;           // pid
    int32_t m_thread_id;     // thread id

    LogLevel m_level; // log level
};

class AsyncLogger {
  public:

    typedef std::shared_ptr<AsyncLogger> s_ptr;

    AsyncLogger(std::string& file_name, std::string &file_path, int max_size);

    void stop();

    void flush();

    void pushLogBuffer(std::vector<std::string> &vec);
 
  public:
    static void* Loop(void *);

  private:

    // m_file_path/m_file_name_yyyymmdd.m_no.log

    std::queue<std::vector<std::string>> m_buffer;

    // log dump 
    std::string m_file_name;
    std::string m_file_path; 

    int m_max_file_size{0}; //Bytes

    sem_t m_semaphore; // initialization
    pthread_t m_thread;

    Mutex m_mutex;
    pthread_cond_t m_condition;

    std::string m_date;
    FILE* m_file_handler {nullptr};

    int m_no {0}; // sequence of log

    bool m_reopen_flag {false};

    bool m_stop_flag {false};
};

class Logger {
  public:
    typedef std::shared_ptr<Logger> s_ptr;

    Logger(LogLevel level);

    void init();

    void pushLog(const std::string &msg);

    void log();

    LogLevel getLogLevel();

    void syncLoop();

  public:
    static Logger *GetGlobalLogger();

    static void InitGlobalLogger();

  private:
    LogLevel m_set_level;

    std::vector<std::string> m_buffer;

    Mutex m_mutex;

    // log dump 
    std::string m_file_name;
    std::string m_file_path; 

    int m_max_file_size{0};

    AsyncLogger::s_ptr m_async_logger{nullptr};
    TimerEvent::s_ptr m_timer_event;
};

} // namespace rocket

#endif
